﻿// Copyright (c) Microsoft Corporation
//
// All rights reserved. 
//
// Licensed under the Apache License, Version 2.0 (the ""License""); you may not use this file 
// except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING 
// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY 
// OR NON-INFRINGEMENT. 
//
// See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.

using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Threading;
using Windows.Data.Pdf;
using Windows.Storage;
using Windows.Storage.Streams;
using GalaSoft.MvvmLight;
using System.Collections.Generic;
using Newtonsoft.Json;
using System.IO;
using TED.WindowsAzure.Unplugged.Mobile.Interfaces;
using TED.WindowsAzure.Unplugged.Mobile.Sync.File;
using TED.WindowsAzure.Unplugged.Mobile.Sync.Folder;
using TED.WindowsAzure.Unplugged.Mobile;
using System.Windows.Input;
using GalaSoft.MvvmLight.Command;
using DPE.TED.OCAF.Win8SampleClient.UI_Helpers;
using GalaSoft.MvvmLight.Messaging;


namespace DPE.TED.OCAF.Win8SampleClient.ViewModels
{
    public class RenderPageViewModel : ViewModelBase
    {

        private IUnpluggedMobileServiceClient _client = App.MobileClient;
        private JsonSerializer _serializer;
        private String _userName;

        private byte[] _placeholderBytes = null;
        private Dictionary<string, int> lookuptable = new Dictionary<string, int>();
        private Task<StorageFile>[] tasks;
        
        private StorageFile _errorFile;
        private CancellationTokenSource _tokenSource;
        private Dictionary<string, ISyncFolder> _commentsFolders;

        /// <summary>
        /// Constructor for new instance of the MainViewModel class. Class must be initialized before first use
        /// </summary>
        public RenderPageViewModel(INavigationService navSvc)
        {
            this.NavigationService = navSvc;

            _commentsFolders = App.CommentsFolders;

            PageRenderCollection = new ObservableCollection<PDFPageViewModel>();
            CommentsCollection = new ObservableCollection<CommentsViewModel>();
        }

        /// <summary>
        /// Initialize class. File name passed to this page is rendered as a pdf file by 
        /// rendering individual jpg files for each page
        /// </summary>
        /// <param name="selected"></param>
        public async void Initialize(PdfViewModel selected)
        {
         
            CancellationToken _token;
            _tokenSource = new CancellationTokenSource();
            _token = _tokenSource.Token;
            _errorFile = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync("Assets\\error.png");

               
            PdfDocument pdfDocument = null;
            _serializer = new JsonSerializer();

            // Get first name of current user
            _userName = await Windows.System.UserProfile.UserInformation.GetFirstNameAsync();

            //Store Pdf File name 
            _FileName = selected.FileName;

        

            // Clear our collection from previous display
            this.PageRenderCollection.Clear();
            this.CommentsCollection.Clear();
            RaisePropertyChanged("CommentsCollection");
            RaisePropertyChanged("PageRenderCollection");

            // Get or Create the container. The convention is comments- followed by the file name
            var containerName = "comments-" + scrubName(_FileName);

            this.AnnotationsContainer = await _client.SyncContext.GetSyncFolderAsync(containerName);
            await this.AnnotationsContainer.ReadAsync();
            ISyncFolder folder;
            var success = _commentsFolders.TryGetValue(containerName, out folder);
            if (!success)
            {
                _commentsFolders.Add(containerName, this.AnnotationsContainer);
            }

      
            // Set page Title
            _PageTitle = _FileName;

            // Render Page
            var stream = await selected.SyncFile.OpenReadAsync();

            if (stream.Size == 0) 
            {
                var msg = new DialogMessage() { Text = "This file could not be rendered." };
                Messenger.Default.Send<DialogMessage>( msg );

                return;
            }

            try
            {
                pdfDocument = await PdfDocument.LoadFromStreamAsync( stream );

            }
            catch (Exception)
            {
                var msg = new DialogMessage() { Text = "This file could not be rendered." };
                Messenger.Default.Send<DialogMessage>( msg );
                
                return;
            }

            Debug.WriteLine("PDF Pages: " + pdfDocument.PageCount);
            

            // Add Placeholder
            StorageFile placeholder = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync("Assets\\placeholder.png");

            // create byte array from placeholder file
            using (IRandomAccessStream placeholderStream = await placeholder.OpenReadAsync())
            {
                using (DataReader reader = new DataReader(stream.GetInputStreamAt(0)))
                {
                    // Display the image in the UI.

                    await reader.LoadAsync((uint)stream.Size);
                    _placeholderBytes = new byte[stream.Size];
                    reader.ReadBytes(_placeholderBytes);
                }
            }


            // render first page
            await RenderPDFPage(pdfDocument, 0, _token);
       
            tasks = new Task<StorageFile>[pdfDocument.PageCount-1];
            if (_token.IsCancellationRequested)
            {
                Debug.WriteLine("Rendering Cancelled before starting Tasks");
                return;
            }

            //Iterate through rest of page
            for (uint i = 1; i < pdfDocument.PageCount; i++)
            {

                // Can't use i in the continuation since it will use the last value rather 
                // than the original when the continuation was created
                await RenderPDFPage(pdfDocument, i, _token);
                if (_token.IsCancellationRequested) {
                    Debug.WriteLine("Rendering Cancelled during creation of Tasks");
                    return;
                }
            }

            //Get comment files
            IEnumerable<ISyncFile> localCommentFiles = ((IEnumerable<ISyncFile>)await AnnotationsContainer.ReadAsync());

            // read files in collection of CommentsViewModels
            foreach (ISyncFile file in localCommentFiles)
            {
                using (IRandomAccessStream readRandomStream = await file.OpenReadAsync())
                {
                    using (var inputStream = readRandomStream.GetInputStreamAt(0))
                    {
                        using (StreamReader sr = new StreamReader(inputStream.AsStreamForRead()))
                        {
                            CommentsViewModel comment = (CommentsViewModel)_serializer.Deserialize(sr, typeof(CommentsViewModel));
                            CommentsCollection.Add(comment);
                            comment.PropertyChanged += newComment_PropertyChanged;
                        }
                    }
                }
            }
        }

        public async void AddComment(string text, double X, double Y, int page, double imageX, double imageY)
        {

            // need to attempt to assign a unique filename accross all users
            var commentID = _FileName + _userName + X + Y;
            CommentsViewModel newComment = new CommentsViewModel(commentID, "", _FileName, X, Y, page, imageX, imageY);
            
            ISyncFile newCommentFile = await AnnotationsContainer.CreateFileAsync(commentID + ".dat");
            await _SaveComment(newComment, newCommentFile);

            CommentsCollection.Add(newComment);
            
            // Subscribe to changes on newComment
            newComment.PropertyChanged += newComment_PropertyChanged;

            // Set Last Comment to enable scrollintoview capability
            SelectedComment = newComment;
        }

        private async Task _SaveComment(CommentsViewModel comment, ISyncFile commentFile)
        {
            // Serialize Comment to file
            using (IRandomAccessStream writeRandomStream = await commentFile.OpenWriteAsync())
            {
                using (var outputStream = writeRandomStream.GetOutputStreamAt(0))
                {
                    using (StreamWriter sw = new StreamWriter(outputStream.AsStreamForWrite()))
                    {
                        _serializer.Serialize(sw, comment);
                        await sw.FlushAsync();
                    }
                }             
            }

            await commentFile.CommitAsync();
        }

        async void newComment_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Text")
            {
                ISyncFile commentFile = await AnnotationsContainer.LookupAsync(((CommentsViewModel)sender).CommentID + ".dat");
                await _SaveComment((CommentsViewModel)sender,commentFile);
            }
     
        }

        private async Task RenderPDFPage(PdfDocument pdfDocument, uint page, CancellationToken token)
        {
            StorageFile jpgFile;

            try
            {
                if (pdfDocument != null && pdfDocument.PageCount > 0)
                {
                    //Get Pdf page
                    var pdfPage = pdfDocument.GetPage(page);
                    if (pdfPage != null)
                    {
                        // next, generate a bitmap of the page
                        StorageFolder tempFolder = ApplicationData.Current.TemporaryFolder;
                        jpgFile = await tempFolder.CreateFileAsync(Guid.NewGuid().ToString() + ".png", CreationCollisionOption.ReplaceExisting);

                        if (jpgFile != null)
                        {
                            IRandomAccessStream randomStream = await jpgFile.OpenAsync(FileAccessMode.ReadWrite);

                            PdfPageRenderOptions pdfPageRenderOptions = new PdfPageRenderOptions();
                            await pdfPage.RenderToStreamAsync(randomStream);

                            randomStream.Dispose();
                            pdfPage.Dispose();
                            if (!token.IsCancellationRequested) { 
                                lookuptable.Add(jpgFile.DisplayName, (int) page);
                                await AddImageFileAsync(jpgFile, (int) page);
                            }
                            else
                            {
                                Debug.WriteLine("Rendering cancelled while rendering " + jpgFile.DisplayName);
                            }

                        }
                        else
                        {
                            jpgFile = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync("Assets\\error.png");
                            await AddImageFileAsync(jpgFile, (int)page);
                        }
                    }
                }

                return;
            }
            catch (Exception err)
            {
                Debug.WriteLine("error rendering page: " + err);
            }

            await AddImageFileAsync(_errorFile, (int)page);

       
        }

        #region RenderPDFPage
        
        private string scrubName(string fileName)
        {
            char[] badchars = "()&^%$#@!~`'[]{}._+= ".ToCharArray();
      
            // all lower case
            fileName = fileName.ToLower();
  
            // change space and non alphanumeric to dashes
            int foundindex = 0;
            do {
                foundindex = fileName.IndexOfAny(badchars,foundindex);
                if (foundindex >= 0) {
                    fileName = fileName.Remove(foundindex,1);
                }
            } while (foundindex >= 0);

            return fileName;
        }
      
        private async Task AddImageFileAsync(StorageFile file, int location)
        {
            // Display the image in the UI.
            byte[] bytes = null;

            // create byte array from file
            using (IRandomAccessStream stream = await file.OpenReadAsync())
            {
                using (DataReader reader = new DataReader(stream.GetInputStreamAt(0)))
                {
                    await reader.LoadAsync((uint)stream.Size);
                    bytes = new byte[stream.Size];
                    reader.ReadBytes(bytes);
                }
            }

            var renderedPage = new PDFPageViewModel();
            renderedPage.FileName = file.Name;
            renderedPage.ImageSource = bytes;
            renderedPage.Page = location;

            // Add to page collection
            PageRenderCollection.Add(renderedPage);

            Debug.WriteLine("adding image" + location);
        }

        #endregion

        private CommentsViewModel _SelectedComment = default( CommentsViewModel );

        public CommentsViewModel SelectedComment
        {
            get
            {
                return _SelectedComment;
            }

            set
            {
                _SelectedComment = value;
                RaisePropertyChanged( "SelectedComment" );
            }
        }

        private string _PageTitle = default(string);

        public string PageTitle
        {
            get
            {
                return _PageTitle;
            }

            set
            {
                _PageTitle = value;
                RaisePropertyChanged("PageTitle");
            }
        }

        private ObservableCollection<PDFPageViewModel> _PageRenderCollection = default(ObservableCollection<PDFPageViewModel>);

        public ObservableCollection<PDFPageViewModel> PageRenderCollection
        {
            get
            {
                return _PageRenderCollection;
            }

            set
            {
                _PageRenderCollection = value;
                RaisePropertyChanged("PageRenderCollection");
            }
        }

        private ObservableCollection<CommentsViewModel> _CommentsCollection = default(ObservableCollection<CommentsViewModel>);

        public ObservableCollection<CommentsViewModel> CommentsCollection
        {
            get
            {
                return _CommentsCollection;
            }

            set
            {
                _CommentsCollection = value;
                RaisePropertyChanged("CommentsCollection");
            }
        }

        private string _FileName = default(string);

        public string FileName
        {
            get
            {
                return _FileName;
            }

            set
            {
                _FileName = value;
                RaisePropertyChanged("FileName");
            }
        }

        protected ISyncFolder AnnotationsContainer { get; set; }
        
        internal INavigationService NavigationService { get; set; }

        private enum RENDEROPTIONS
        {
            NORMAL,
            ZOOM,
            PORTION
        }

        internal void CancelAllRendering()
        {
                _tokenSource.Cancel();
        }
    }
}
